import { posix } from 'path';
import {
  Disposable,
  Event,
  EventEmitter,
  FileChangeEvent,
  FileStat,
  FileSystemError,
  FileSystemProvider,
  FileType,
  Uri,
} from 'vscode';
import { GlobalConst } from '../../constants';
import { api } from '../../service';
import { Directory, Entry, File } from './entry';
import { SystemFile } from './system-file';

/**
 * iBizSys 系统的文件系统
 *
 * @description 使用时第一级根目录一定是系统id，ibizsystem:/${系统标识}/。例: ibizsystem:/ebsx/
 * @author chitanda
 * @date 2022-12-14 11:12:55
 * @export
 * @class SystemFS
 * @implements {FileSystemProvider}
 */
export class SystemFS implements FileSystemProvider {
  readonly root = new Directory(new SystemFile('/', '/'));

  private ignoreReg = /\/(.vscode|.git|.devcontainer|node_modules)/;

  private emitter = new EventEmitter<FileChangeEvent[]>();

  onDidChangeFile: Event<FileChangeEvent[]> = this.emitter.event;

  watch(
    _uri: Uri,
    _options: {
      readonly recursive: boolean;
      readonly excludes: readonly string[];
    },
  ): Disposable {
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    return new Disposable(() => {});
  }

  async stat(uri: Uri): Promise<FileStat> {
    const stat = await this.findStat(uri);
    return stat;
  }

  async readDirectory(uri: Uri): Promise<[string, FileType][]> {
    const arr: [string, FileType][] = [];
    const entry = this._lookup(uri, true);
    if (entry) {
      if (entry instanceof Directory) {
        entry.entries.forEach(item => {
          arr.push([
            item.name,
            item instanceof Directory ? FileType.Directory : FileType.File,
          ]);
        });
      }
    }
    return arr;
  }

  async createDirectory(_uri: Uri): Promise<void> {
    throw new Error('暂未支持创建文件夹');
  }

  async readFile(uri: Uri): Promise<Uint8Array> {
    const file = this._lookupAsFile(uri, false);
    return file.data;
  }

  async writeFile(
    uri: Uri,
    content: Uint8Array,
    _options?: { readonly create: boolean; readonly overwrite: boolean },
  ): Promise<void> {
    const file = this._lookupAsFile(uri, true);
    const parent = this.getParent(uri);
    const code = content.toString();
    const bol = await api.system.hotCode(
      parent.content.data.pssystemid,
      file.content.data.id,
      code,
    );
    if (bol) {
      file.content.data.templcode = code;
    }
  }

  async delete(
    _uri: Uri,
    _options: { readonly recursive: boolean },
  ): Promise<void> {
    // 暂未支持删除
    throw new Error('暂未支持删除');
  }

  async rename(
    _oldUri: Uri,
    _newUri: Uri,
    _options: { readonly overwrite: boolean },
  ): Promise<void> {
    // 暂未支持修改名称
    throw new Error('暂未支持修改名称');
  }

  protected async findStat(uri: Uri): Promise<Entry> {
    const { path } = uri;
    if (this.ignoreReg.test(path)) {
      throw FileSystemError.FileNotFound();
    }
    const entry = this._lookup(uri, true);
    if (entry) {
      return entry;
    }
    if (!entry) {
      if (path.lastIndexOf('/') === 0) {
        // 只有一个 / 开头，认为是系统根目录，去加载系统信息
        const id = path.replace('/', '');
        if (id) {
          const data = await api.system.get(id);
          if (data) {
            // 添加系统根目录
            const dir = new Directory(new SystemFile(path, path, '', data));
            this.root.entries.set(id, dir);
            // 获取系统对应插件清单
            const plugins = await api.system.fetchPlugins(id);
            // 设置插件清单进子目录
            plugins.forEach(plugin => {
              dir.entries.set(
                `${plugin.name}.groovy`,
                new File(
                  new SystemFile(
                    plugin.name,
                    `${path}/${plugin.name}`,
                    '.groovy',
                    plugin,
                  ),
                ),
              );
            });
            return dir;
          }
        }
      }
    }
    throw FileSystemError.FileNotFound();
  }

  private _lookup(uri: Uri, silent: false): Entry;

  private _lookup(uri: Uri, silent: boolean): Entry | undefined;

  private _lookup(uri: Uri, silent: boolean): Entry | undefined {
    const parts = uri.path.split('/');
    let entry: Entry = this.root;
    for (const part of parts) {
      if (!part) {
        continue;
      }
      let child: Entry | undefined;
      if (entry instanceof Directory) {
        child = entry.entries.get(part);
      }
      if (!child) {
        if (!silent) {
          throw FileSystemError.FileNotFound(uri);
        } else {
          return undefined;
        }
      }
      entry = child;
    }
    return entry;
  }

  private _lookupAsFile(uri: Uri, silent: boolean): File {
    const entry = this._lookup(uri, silent);
    if (entry instanceof File) {
      return entry;
    }
    throw FileSystemError.FileIsADirectory(uri);
  }

  private _lookupAsDirectory(uri: Uri, silent: boolean): Directory {
    const entry = this._lookup(uri, silent);
    if (entry instanceof Directory) {
      return entry;
    }
    throw FileSystemError.FileNotADirectory(uri);
  }

  /**
   * 查找父目录
   *
   * @author chitanda
   * @date 2021-11-17 19:11:56
   * @private
   * @param {Uri} uri 当前目录
   * @param {number} [offset=1] 偏移几级查找父，默认为 1 级
   * @return {*}  {Directory}
   */
  private _lookupParentDirectory(uri: Uri, offset: number = 1): Directory {
    let { path } = uri;
    for (let i = 0; i < offset; i++) {
      path = posix.dirname(path);
    }
    const dirname = uri.with({ path });
    return this._lookupAsDirectory(dirname, false);
  }

  /**
   * 获取父目录
   *
   * @param uri
   * @param offset
   * @returns
   */
  getParent(uri: Uri, offset: number = 1): Directory {
    return this._lookupParentDirectory(uri, offset);
  }

  /**
   * 获取文件对象
   *
   * @author chitanda
   * @date 2021-11-15 14:11:11
   * @param {Uri} uri
   * @return {*}  {(File | undefined)}
   */
  getFile(uri: Uri): File | undefined {
    return this._lookup(uri, true) as File;
  }

  /**
   * 根据文件路径，拼接当前文件协议并转成 Uri 对象
   *
   * @param path
   * @return {*}  {Uri}
   */
  getUri(path: string): Uri {
    let fullPath = path;
    if (!path.startsWith(GlobalConst.FS_PROTOCOL.SYSTEM)) {
      fullPath = GlobalConst.FS_PROTOCOL.SYSTEM + path;
    }
    return Uri.parse(fullPath);
  }
}
